package com.woniu.service.impl;

import com.woniu.entity.Book;
import com.woniu.entity.dto.SecBookDTO;
import com.woniu.mapper.BookMapper;
import com.woniu.service.BookService;
import com.woniu.util.ResData;
import com.woniu.util.ResEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookMapper bookMapper;
    ReentrantLock lock = new ReentrantLock();
    //StringRedisTemplate == RedisTemplate<String,String>
    /**
     * 怎么把对象写入数据库
     *
     * 写入数据库经过 i/o，但前提是把对象转成字符
     * 对象 ->（序列化） 字符 -> 二进制 -> 文件（数据库）
     *
     * redis 泛型是Object 使用的序列化方式是 JDK 自带的，会转成二进制
     *      但是为了 redis 中数据可视化，所以要重写序列化方式
     *      之前泛型 String，没有出现二进制，因为 String 已经重写了序列化方式
     *
     */
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @Override
    public ResData<?> queryAll() {
        try {
            List<Book> books = bookMapper.queryAll();
            ResData resData = new ResData<>().ok(books);
            return resData;
        } catch (Exception e) {
            //抛异常，使用统一异常处理类
            //返回
            return new ResData<>().fail(ResEnum.FAIL);
        }
    }

    /**
     * 给查询书籍详情功能做缓存
     * 做缓存的步骤
     *  1：先查 redis，有就直接返回
     *  2：没有，查询数据库
     *
     *  做缓存带来的问题
     *  1：缓存穿透的问题
     *     原因：数据库，和 redis 都不存在该数据
     *     解决：1：缓存空对象
     *              优点：比较简单；缺点：容易往Redis里存大量脏数据
     *          2：用布隆过滤器（底层是通过 redis 的一个 bitmap 数据类型实现的，bitmap：二进制数组）
     * @param bookId
     * @return
     */
    @Override
    public ResData<?> queryBookDetailByBookId(Integer bookId) { //bookId=9999
        try {
            //先获取 redis 中书籍信息
            Book redisBook = (Book) redisTemplate.opsForValue().get("bookDetail-" + bookId);
            if (redisBook != null) {
                return new ResData<>().ok(redisBook);
            }
            if(lock.tryLock(2, TimeUnit.MILLISECONDS)){
                Book book = bookMapper.queryBookDetailByBookId(bookId);
                //把书籍的信息放入redis
                redisTemplate.opsForValue().set("bookDetail-"+bookId,book == null?new Book():book);
                return new ResData<>().ok(book);
            }else {
                //没有获取到锁
                return new ResData<>().fail(ResEnum.FAIL);
            }
        } catch (Exception e) {
            return new ResData<>().fail(ResEnum.FAIL);
        }finally {
            //判断线程是否持有锁
            if(lock.isHeldByCurrentThread()){
                lock.unlock();  //释放锁
            }
        }
    }

    @Override
    public ResData<?> querySecActiveAll() {
        try {
            List<SecBookDTO> books = bookMapper.querySecActiveAll();
            ResData resData = new ResData<>().ok(books);
            return resData;
        } catch (Exception e) {
            //抛异常，使用统一异常处理类
            //返回
            return new ResData<>().fail(ResEnum.FAIL);
        }
    }

    /**
     * 采用延迟双删，解决缓存数据不一致的问题
     */
    public ResData<?> update(Book book){
        //1、删除 redis
        redisTemplate.delete("bookDetail-"+book.getId());
        //2、操作数据库（修改数据）
        //bookMapper.update(book);
        //3、延迟 200 ms
        //Thread.sleep(200);
        //4、删除 redis
        redisTemplate.delete("bookDetail-"+book.getId());
        return new ResData<>().ok();
    }
}
